/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.fxbank.netbeans.plugins.utils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.fxbank.netbeans.plugins.utils.Constants.*;

/**
 * Узел XML-дерева <code>XMLTree</code>
 * @author bukarev-aa
 */
public class XMLNode implements Serializable {
    private String name;
    private Map<String, String> attr;
    private List<XMLNode> childs;
    private String text = EMPTY_STRING;
    private XMLNode parentNode = null;

    public XMLNode() {
        this(null);
    }
    
    /**
     * Конструктор нового узла
     * @param name имя узла
     */
    public XMLNode(String name) {
        setName(name);
    }
    
    public void setName(String name) {
        this.name = name == null ? EMPTY_STRING : name.trim();
    }
    
    /**
     * Имя XML-элемента
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 
     * @return вернет родительский узел (у корня родительский узел равен null)
     */
    public XMLNode getParentNode() {
        return this.parentNode;
    }

    /**
     * Добавить дочерний узел
     * @param child дочерний узел
     */
    public void addChild(XMLNode child) {
        // Если список дочерних узлов пустой - создаём его
        if(childs == null) childs = new LinkedList<XMLNode>();
        child.parentNode = this;
        childs.add(child);
    }
    
    /**
     * Возвращает список дочерних элементов
     * @return
     */
    public List<XMLNode> getChilds() {
        return childs == null ? new LinkedList<XMLNode>() : childs;
    }
    
    /**
     * Установить текстовое содержимое
     * Содержимое тримится
     * @param text
     */
    public void setText(String text) {
        this.text = text == null ? EMPTY_STRING : text.trim();
    }

    /**
     * Установить текстовое содержимое БЕЗ обрезки пробелов по краям
     * Данный метод используется только при работе SAX-парсера для сохранения текста без потерь
     * @param text
     */
    void setTextWithoutTrimming(String text) {
        this.text = text == null ? EMPTY_STRING : text;
    }

    /**
     * Получить текст
     * @return
     */
    public String getText() {
        return text;
    }
    
    /**
     * Добавить атрибут
     * @param attrName имя атрибута
     * @param attrValue значение атрибута
     */
    public void addAttr(String attrName, String attrValue) {
        if(attr == null) attr = new HashMap<String, String>();
        attr.put(attrName, attrValue);
    }
    
    /**
     * Удалить атрибут
     * @param attrName имя атрибута
     */
    public void removeAttr(String attrName) {
        if(attr != null) {
            attr.remove(attrName);
            if(attr.size() == 0) attr = null;
        }
    }
    
    /**
     * Получить значение атрибута
     * @param key имя атрибута
     * @return значение атрибута
     */
    public String getAttr(String key) {
        String val = attr == null ? null : attr.get(key);
        return val == null ? EMPTY_STRING : val;
    }

    /**
     * Получить все атрибуты
     * @return
     */
    public Set<Map.Entry<String, String>> getAttributes() {
        return attr == null? null: attr.entrySet();
    }
    
    private StringBuilder toStringBuilder() {
        StringBuilder sb = new StringBuilder();
        boolean addCurrentNode = name != null && !name.isEmpty();
        if(addCurrentNode) {
            sb.append('<').append(name);
            if(attr != null) {
                for(Map.Entry<String, String> entry : attr.entrySet()) {
                    sb.append(' ').append(entry.getKey()).append("=\"").append(XMLUtils.encodeAttributeValue(entry.getValue())).append('"');
                }   
            }
            sb.append('>');
        }
        if(childs != null) {
            for(XMLNode child: childs)
                sb.append(child.toStringBuilder());
        }
        if(text != null) sb.append(XMLUtils.encodeElementValue(text));
        if(addCurrentNode) {
            sb.append("</" + name + '>');
        }
        return sb;
    }
    
    /**
     * Простейший обработчик XPath<br/>
     * Понимает формат /node1/node2 - Возвращает все узлы, соответсвующие шаблону<br/>
     * Можно использовать символ '*'
     * @param path шаблон в формате XPath
     * @return набор узлов, соответствующих шаблону
     */
    public List<XMLNode> getXPathNodes(String path) {
        List<XMLNode> foundedPrev = new ArrayList<XMLNode>();
        List<XMLNode> founded = new ArrayList<XMLNode>();
        founded.add(this);
        String[] p = path.split("/");

        for (String s : p) {
            if (s.length() == 0) {
                continue;
            }
            if (founded.size() == 0) {
                return EMPTY_NODES;
            } else {
                foundedPrev = founded;
                founded = new ArrayList<XMLNode>();
            }
            for (XMLNode node : foundedPrev) {
                if (node.getChilds() != null) {
                    for (XMLNode xmln : node.getChilds()) {
                        if (ANY_NODE.equals(s) || xmln.name.equals(s)) {
                            founded.add(xmln);
                        }
                    }
                }
            }
        }
        return founded;
    }

    private final static List<XMLNode> EMPTY_NODES = new ArrayList<XMLNode>();
    private final static String ANY_NODE = "*";

    /**
     * Возвращает текстовое представление узла в виде XML
     * @return
     */
    @Override
    public String toString() {
        return toStringBuilder().toString();
    }
}

